/*
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package std.containers

export namespace containers {

    type Undefinable{{T}} = {{T}} | undefined

    export class Undefinable{{T}}Array {
        /* const */ static ArrayInitSize : int = 16

        private init(capacity: int, val: Undefinable{{T}}): void {
            this.data = new Undefinable{{T}}[capacity]
            for (let i = 0; i < this.data.length; ++i) {
                this.data[i] = val
            }
            this.curSize = capacity
        }

        /**
        * Constructs new Undefinable{{T}}Array with respect to capacity and initial value
        *
        * @param capacity
        *
        * @param val
        */
        constructor(capacity: int, val: Undefinable{{T}}) {
            this.init(capacity, val)
        }

        /**
        * Constructs new Undefinable{{T}}Array with required capacity
        *
        * @param capacity
        */
        constructor(capacity: int) {
            this.data = new Undefinable{{T}}[capacity]
            this.curSize = 0
        }

        /**
        * Constructs new empty Undefinable{{T}}Array
        */
        constructor() {
            this(Undefinable{{T}}Array.ArrayInitSize)
        }

        /**
        * Increases capacity if passed argument is greater than current capacity
        *
        * @param capacity
        */
        public reserve(capacity: int): void {
            if (this.data.length < capacity) {
                let newData = new Undefinable{{T}}[capacity]
                for (let i = 0; i < this.curSize; ++i) {
                newData[i] = this.data[i]
                }
                this.data = newData
            }
        }

    /**
     * Pops a value from the end of the List
     *
     * @returns popped value
     */
    public popBack(): Undefinable{{T}} {
        arktest.assertLT(0, this.curSize, "No data to popBack in Undefinable{{T}}Array!")
        --this.curSize
        return this.data[this.curSize]
    }

        private static getNewCapacity(currentCapacity: int): int {
            const fastGrowThreshold = 8192
            const multiplier = 2
            if (currentCapacity < fastGrowThreshold) {
                // Adding 4 to jump over 0
                return (currentCapacity + 4) * multiplier * 2
            } else {
                return currentCapacity * multiplier
            }
        }

        /**
        * Pushes a value to the end of the array
        *
        * @param e value to push
        */
        public pushBack(e: Undefinable{{T}}): void {
            if (this.curSize == this.data.length) {
                let newData = new Undefinable{{T}}[Undefinable{{T}}Array.getNewCapacity(this.data.length)]
                for (let i = 0; i < this.curSize; ++i) {
                    newData[i] = this.data[i]
                }
                this.data = newData
            }
            this.data[this.curSize] = e
            ++this.curSize
        }

        /**
        * Pops a value from the end of the List
        *
        * @returns popped value
        */
        public popBack(): Undefinable{{T}} {
            assertLT(0, this.curSize, "No data to popBack in Undefinable{{T}}Array!")
            --this.curSize
            return this.data[this.curSize]
        }

        /**
        * Returns number of elements in the List
        */
        public size(): int {
            return this.curSize
        }

        /**
        * Returns an element at the specified index
        *
        * @param index element position
        *
        * @returns an element
        */
        public at(index: int): Undefinable{{T}} {
            return this.data[index]
        }

        /**
        * Sets an element at the specified index
        *
        * @param index element position
        *
        * @param e new value
        */
        public set(index: int, e: Undefinable{{T}}): void {
            this.data[index] = e
        }

        private data: Undefinable{{T}}[] = []
        private curSize: int
    }
}
